package com.myk.game.gobangchess.rooms;

import com.myk.game.gobangchess.CustomBottom;
import com.myk.game.gobangchess.DataManager;
import com.myk.game.gobangchess.Room;
import com.myk.game.gobangchess.Sign;
import com.myk.game.gobangchess.constants.BoardStyleData;
import com.myk.game.gobangchess.constants.ColorCost;
import com.myk.game.gobangchess.constants.WinMode;
import com.myk.game.gobangchess.signs.ColorSign;
import com.myk.game.gobangchess.signs.FigureSign;
import com.myk.game.gobangchess.signs.GroundSign;
import com.myk.game.gobangchess.signs.LineSign;
import com.myk.game.gobangchess.signs.PathSign;
import com.myk.game.gobangchess.signs.TextSign;
import com.myk.game.gobangchess.signs.TitleSign;
import com.myk.game.gobangchess.utils.GridsUtils;
import com.myk.game.gobangchess.utils.Pair;
//import com.sun.istack.internal.NotNull;

import java.awt.Point;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Queue;
import java.util.Random;
import java.util.Set;
import java.util.TreeMap;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Collectors;

/**
 * 七彩巴洛克
 */
public class NanaBaroccoRoom extends Room {

    private final static int[][] MOVE_DIRECTION = new int[][]{{1, 0}, {-1, 0}, {0, 1}, {0, -1}};
    private final static Random RANDOM = new Random();
    private final static int BOARD_SIZE = 10;
    private AtomicInteger readyStart = new AtomicInteger();
    private List<Sign> chessboardStyle; //不变的棋盘样式
    private Board board;

    public NanaBaroccoRoom(String roomName) {
        super(roomName, "夏日大作，速速来玩！！！\n（测试中。。。）", "七彩巴洛克", BoardStyleData.getGoBangStyle(BOARD_SIZE, BOARD_SIZE), true, false);
        initChessboardStyle();
    }

    private static String rgbToHex(int r, int g, int b) {
        return String.format("#FF%02X%02X%02X", r, g, b);
    }

    private static <E> E weightSelect(Map<E, Double> valueWeights) {
        TreeMap<Double, E> map = new TreeMap<>();
        double total = 0;
        for (Entry<E, Double> entry : valueWeights.entrySet()) {
            if (entry.getValue() > 0) {
                total += entry.getValue();
                map.put(total, entry.getKey());
            }
        }
        Entry<Double, E> entry = null;
        while (entry == null) {
            entry = map.higherEntry(RANDOM.nextDouble() * total);
        }
        return entry.getValue();
    }

    public static void main(String[] args) {
        Board b = new Board();
        b.firstAttribute = Attribute.Water;
        b.secondAttribute = Attribute.Light;
        int[][] g = b.randomGround();
        for (int i = 0; i < g.length; i++) {
            for (int j = 0; j < g[i].length; j++) {
                System.out.print(g[i][j]);
            }
            System.out.println();
        }

    }

    //定义棋盘样式
    private void initChessboardStyle() {
        int row = curGrids.length;
        int col = curGrids[0].length;
        chessboardStyle = new ArrayList<>();
        //绘制格子颜色
        chessboardStyle.add(new GroundSign(getIndex(0, 0), getIndex(row - 1, col - 1), "#FFCCCCC0", "#00353119", 1.2f, 0, 0)); //外边框
        //绘制图纹
        List<Integer> allIds = new ArrayList<>();
        for (int i = 0; i < row; i++) {
            for (int j = 0; j < col; j++) {
                allIds.add(getIndex(i, j));
            }
        }
        FigureSign figure = FigureSign.makeBatchSingleFigure(allIds, "#99A5A199");
        figure.setSideExpArc(1, 1, 1, 1, 1, 1, 1, 1);
        figure.setCompCenArc(1, 1, 1, 1);
        chessboardStyle.add(figure);
        //绘制格子线
        for (int i = 1; i < row; i++) {
            List<Pair<Integer, Integer>> nodes1 = new ArrayList<>();
            nodes1.add(new Pair<>(getIndex(i, 0), 0)); //起点
            nodes1.add(new Pair<>(getIndex(i, col - 1), 2)); //终点
            chessboardStyle.add(new PathSign(nodes1, "#AA959190", "#00000000", 1f, 0));
        }
        for (int j = 1; j < col; j++) {
            List<Pair<Integer, Integer>> nodes1 = new ArrayList<>();
            nodes1.add(new Pair<>(getIndex(0, j), 0)); //起点
            nodes1.add(new Pair<>(getIndex(row - 1, j), 6)); //终点
            chessboardStyle.add(new PathSign(nodes1, "#AA959190", "#00000000", 1f, 0));
        }
        //绘制大外边框
        chessboardStyle.add(new GroundSign(getIndex(0, 0), getIndex(row - 1, col - 1), "#005DC7E7", "#FF353119", 1.2f, 0, 0)); //外边框
    }

    @Override
    protected void onStartPrepare() {
        readyStart.set(0);
        board = new Board();
        if (player_black == player_white) {
            //个人模式测试生成地图
            player_black.registerCustomBottom(new CustomBottom(this, "快速重开", p -> {
                board = new Board();
                noticeAllRefreshGameInfo();
            }));
            player_black.registerCustomBottom(new CustomBottom(this, "测试颜色黑", p -> {
                board = new Board("colorB");
                noticeAllRefreshGameInfo();
            }));
            player_black.registerCustomBottom(new CustomBottom(this, "测试颜色白", p -> {
                board = new Board("colorW");
                noticeAllRefreshGameInfo();
            }));
            noticeBlackMove();
        }
        else {
            //双人模式支持协商重开
            player_black.registerCustomBottom(new CustomBottom(this, "刷新地图", p -> {
                p.clearCustomBottom();
                p.dispatchCustomBottom();
                board = new Board();
                if (readyStart.addAndGet(1) == 3) {
                    noticeBlackMove();
                }
                else {
                    noticeAllRefreshGameInfo();
                }
            }));
            player_black.registerCustomBottom(new CustomBottom(this, "准备开始", p -> {
                p.clearCustomBottom();
                p.dispatchCustomBottom();
                if (readyStart.addAndGet(1) == 3) {
                    noticeBlackMove();
                }
                else {
                    noticeAllRefreshGameInfo();
                }
            }));
            player_white.registerCustomBottom(new CustomBottom(this, "刷新地图", p -> {
                p.clearCustomBottom();
                p.dispatchCustomBottom();
                board = new Board();
                if (readyStart.addAndGet(2) == 3) {
                    noticeBlackMove();
                }
                else {
                    noticeAllRefreshGameInfo();
                }
            }));
            player_white.registerCustomBottom(new CustomBottom(this, "准备开始", p -> {
                p.clearCustomBottom();
                p.dispatchCustomBottom();
                if (readyStart.addAndGet(2) == 3) {
                    noticeBlackMove();
                }
                else {
                    noticeAllRefreshGameInfo();
                }
            }));
            noticeNoMove();
        }
    }

    @Override
    public String getGameInfo() {
        curGrids = GridsUtils.copyGrids(originGrids);
        signList.clear();
        signList.addAll(chessboardStyle);
        if (overFlag > 0 && board != null) {
            board.render(this);
        }
        else {
            float titleSize = 0.3f;
            float bY = (curGrids.length + 0.2f) / curGrids.length;
            signList.add(new TitleSign(0, bY, 2, "增益效果只有同属性棋子能够享受", "#FF000000", titleSize));
            signList.add(new TitleSign(0.5f, bY, 2, "减益效果只有同属性棋子能够免疫", "#FF000000", titleSize));
        }
        return super.getGameInfo();
    }

    @Override
    public String getRuleText() {
        return "七彩巴洛克（7-Barocco）0.7.7版本\n" +
                "\n" +
                "{{【概念介绍】@color=#000000@scale=1.2}}\n" +
                "[棋盘]\n" +
                "使用10x10的正方形格棋盘\n" +
                "\n" +
                "[棋子]\n" +
                "数字0到9的棋子共100枚，双方各50枚\n" +
                "开局前，所有棋子将被打乱并暗置在棋盘格上\n" +
                "\n" +
                "[色调]\n" +
                "每局游戏开始前，随机挑选2种属性作为本局色调\n" +
                "色调有分主次，棋盘及棋子将按本局色调进行上色\n" +
                "棋盘：其中40个格子以3：2比例上色\n" +
                "棋子：双方所有棋子以1：1比例上色\n" +
                "————————————————————\n" +
                "{{【详细规则】@color=#000000@scale=1.2}}\n" +
                "[行动回合]\n" +
                "轮流行动，可选行动种类如下：\n" +
                "（A）翻面一枚被暗置的棋子\n" +
                "（B）控制一枚己方棋子走棋\n" +
                "\n" +
                "[棋子走法]\n" +
                "移动：直向不超过<数字>格\n" +
                "攻击：移动至敌人的位置，占位杀\n" +
                "\n" +
                "[特殊规则]\n" +
                "（1）攻击比自己小且非因数的棋子时，占位吃子\n" +
                "（2）攻击为自己因数或倍数的棋子时，同归于尽\n" +
                "（3）攻击比自己大且非自己倍数的棋子时，自杀\n" +
                "（4）棋子0只能被对方非0明子中最小的棋子消灭\n" +
                "（5）未翻开暗子不可被吃，无法被任何效果影响\n" +
                "\n" +
                "[胜负规则]\n" +
                "（A）落败条件：失去所有0\n" +
                "（B）落败条件：无棋可走\n" +
                "————————————————————\n" +
                "{{【色调图鉴】@color=#000000@scale=1.2}}\n" +
                "[地形效果]\n" +
                "{{运河（水蓝色）@color=" + Attribute.Water.groundColor + "}}：[增益]可沿运河地形移动任意次\n" +
                "{{焚场（橙红色）@color=" + Attribute.Fire.groundColor + "}}：[增益]自杀→同归于尽，同归于尽→占位吃\n" +
                "{{棘丛（深绿色）@color=" + Attribute.Thorn.groundColor + "}}：[减益]走法距离降为1，且不可吃子\n" +
                "{{陷阱（棕黄色）@color=" + Attribute.Earth.groundColor + "}}：[减益]会被任意数字占位吃子\n" +
                "{{凌波（天青色）@color=" + Attribute.Float.groundColor + "}}：[增益]走法可越过1枚任意棋子\n" +
                "{{行宫（金黄色）@color=" + Attribute.Gold.groundColor + "}}：[减益]不可从其它地形格进入行宫地形\n" +
                "{{极光（亮粉色）@color=" + Attribute.Light.groundColor + "}}：[增益]可传送至同一行/列的极光地形\n" +
                "{{夜影（深蓝色）@color=" + Attribute.Night.groundColor + "}}：[减益]移动经过夜影地形时结束回合\n" +
                "{{天霆（纯紫色）@color=" + Attribute.Thunder.groundColor + "}}：[增益]攻击波及目标的相邻棋子\n" +
                "\n" +
                "[地形约束]\n" +
                "（1）增益效果只有同属性棋子能够享受\n" +
                "（2）减益效果只有同属性棋子能够免疫\n" +
                "\n" +
                "更多信息请加设计师个人QQ群657062274了解。";
    }

    @Override
    public void doRule(int row, int col, int value) {
        if (board.touch(row, col, value)) {
            stepAdd();
            if (cur == 1) {
                noticeWhiteMove();
            }
            else {
                noticeBlackMove();
            }
        }
        else {
            noticeAllRefreshGameInfo();
        }
        switch (board.checkWin(cur)) {
            case 1:
                doOver(WinMode.BLACK_WIN, "黑胜");
                break;
            case 2:
                doOver(WinMode.WHITE_WIN, "白胜");
                break;
            case 3:
                doOver(WinMode.BOTH_DRAW, "平局");
                break;
        }
    }

    @Override
    public int getCountdownSec() {
        return 21;
    }

    private enum Attribute {
        Empty(0, "空地", "#FF000000", "没有特殊效果的普通地形", new int[][]{}, true),
        Gold(1, "行宫", rgbToHex(235, 216, 0), "[减益]不可从其它地形格进入行宫地形", new int[][]{{1, 0}, {2, 0}, {2, 1}}, true),
        Thorn(2, "棘丛", rgbToHex(35, 177, 77), "[减益]走法距离降为1，且不可吃子", new int[][]{{1, 0}, {2, 0}, {3, 0}}, true),
        Water(3, "运河", rgbToHex(4, 163, 232), "[增益]可沿运河地形移动任意次", new int[][]{}, true), //需要特殊处理
        Fire(4, "焚场", rgbToHex(238, 69, 30), "[增益]自杀→同归于尽，同归于尽→占位吃", new int[][]{{1, 0}}, true),
        Earth(5, "陷阱", rgbToHex(179, 112, 42), "[减益]会被任意数字占位吃子", new int[][]{{-1, 1}, {1, 1}, {0, 2}}, true),
        Float(6, "凌波", rgbToHex(0, 255, 128), "[增益]走法可越过1枚任意棋子", new int[][]{{1, 0}, {2, 0}, {1, 1}}, true),
        Thunder(7, "天霆", rgbToHex(193, 55, 253), "[增益]攻击波及目标的相邻棋子", new int[][]{{1, 0}, {1, 1}, {2, 1}}, false),
        Light(8, "极光", rgbToHex(253, 130, 254), "[增益]可传送至同一行/列的极光地形", new int[][]{}, false), //需要特殊处理
        Night(9, "夜影", rgbToHex(64, 71, 204), "[减益]移动经过夜影地形时结束回合", new int[][]{}, false);


        public final int value;
        public final String name;
        public final String color;
        public final String groundColor;
        public final String describe;
        public final int[][] shape;
        public final boolean canNeighbor;

        Attribute(int value, String name, String color, String describe, int[][] shape, boolean canNeighbor) {
            this.value = value;
            this.name = name;
            this.color = color;
            this.groundColor = color.replace("#FF", "#CC");
            this.describe = describe;
            this.shape = shape;
            this.canNeighbor = canNeighbor;
        }

        public static Attribute of(int value) {
            switch (value) {
                case 1:
                    return Gold;
                case 2:
                    return Thorn;
                case 3:
                    return Water;
                case 4:
                    return Fire;
                case 5:
                    return Earth;
                case 6:
                    return Float;
                case 7:
                    return Thunder;
                case 8:
                    return Light;
                case 9:
                    return Night;
                default:
                    return Empty;
            }
        }

    }

    private enum Move {
        PASS(0, true),
        NOT_ALLOW(9, false),
        EAT(9, true),
        BOOM(9, true),
        SUICIDE(9, true),
        STAND(9, true),
        NOT_ALLOW_PASS(1, false),
        EAT_PASS(1, true),
        BOOM_PASS(1, true),
        SUICIDE_PASS(1, true),
        NOT_ALLOW_GOLD(0, false);

        final int passCount; // 消耗可通过性的记述，主要是为了风而设置的，先暂时用9来表示不可通过完全消耗
        final boolean canStay;


        Move(int passCount, boolean canStay) {
            this.passCount = passCount;
            this.canStay = canStay;
        }
    }

    private static class Ground {

        public static final Ground EMPTY = new Ground(0, -1, -1);
        public final Attribute attribute;
        public final Point position;

        private Ground(int attribute, int x, int y) {
            this(Attribute.of(attribute), x, y);

        }

        private Ground(Attribute attribute, int x, int y) {
            this.attribute = attribute;
            this.position = new Point(x, y);
        }

        public void render(Room room, Map<String, List<Integer>> colorMap) {
            // 绘制这个土地
            int index = room.getIndex(position.x, position.y);
            colorMap.computeIfAbsent(attribute.groundColor, k -> new ArrayList<>()).add(index);
//            room.signList.add(new ColorSign(index, attribute.color));
        }
    }

    private static class Chess {

        public final Attribute attribute;
        public final Point position;
        public final Map<Point, Move> moveRange = new HashMap<>();
        private final Board board;
        private final int side;
        private final int num;
        private final int id; // 相同的数字和属性用id来区别
        public boolean isRevealed = false;

        private Chess(Board board, int side, int num, Attribute attribute, int x, int y, int id) {
            this.board = board;
            this.attribute = attribute;
            this.position = new Point(x, y);
            this.side = side;
            this.num = num;
            this.id = id;
        }

        private String getName() {
            return Integer.toString(num);
//            return "⓪➀➁➂➃➄➅➆➇➈➉".substring(num, num + 1);
        }

        private Move canMove(int x, int y, boolean isThunderEffect) {
            final Move moveValue;
            Chess enemy = board.getChess(x, y);
            Ground enemyGround = board.getGround(x, y);
            if (enemy == null) {
                moveValue = board.checkCanMove(this, enemyGround);
            }
            else {
                moveValue = board.checkCanEat(this, enemy, isThunderEffect);
            }
            return moveValue;
        }

        private Move canMove(int x, int y) {
            return canMove(x, y, false);
        }

        private void bfsWaterMoveRange() {
            Queue<Point> que = new LinkedList<>();
            Set<Point> seen = new HashSet<>();
            que.add(position);
            seen.add(position);
            while (!que.isEmpty()) {
                Point point = que.poll();
                for (int[] dxdy : MOVE_DIRECTION) {
                    final int x = point.x + dxdy[0];
                    final int y = point.y + dxdy[1];
                    if (board.getGround(x, y).attribute == Attribute.Water) {
                        Point newPoint = new Point(x, y);
                        if (seen.contains(newPoint)) {
                            continue;
                        }
                        seen.add(point);
                        Move moveValue = canMove(x, y);
                        if (moveValue.canStay) {
                            moveRange.put(newPoint, moveValue);
                        }
                        if (moveValue.passCount == 0) {
                            que.add(newPoint);
                        }
                    }
                }
            }
        }

        public void updateMoveRange() {
            moveRange.clear();
            Ground ground = board.getGround(position.x, position.y);
            final boolean effect = isRevealed && attribute == ground.attribute;
            final int moveNum;
            if (!effect && ground.attribute == Attribute.Thorn) {
                // 己方 木
                moveNum = 1;
            }
            else if (effect && ground.attribute == Attribute.Float) {
                moveNum = Integer.MAX_VALUE;
            }
            else {
                moveNum = num;
            }
            if (effect && attribute == Attribute.Water) {
                // 己方 水
                bfsWaterMoveRange();
            }
            for (int[] dxdy : MOVE_DIRECTION) {
                int passCount = 0;
                int x = position.x;
                int y = position.y;
                for (int i = 0; i < moveNum; i++) {
                    x += dxdy[0];
                    y += dxdy[1];
                    if (board.notInBoard(x, y)) {
                        break;
                    }
                    final Move moveValue = canMove(x, y);
                    if (moveValue.canStay) {
                        moveRange.put(new Point(x, y), moveValue);
                    }
                    passCount += moveValue.passCount;
                    if (passCount > 1 || (passCount == 1 && (!effect || attribute != Attribute.Float))) {
                        break;
                    }
                }
                if (effect && attribute == Attribute.Light) {
                    // 己方 光
                    while (true) {
                        x += dxdy[0];
                        y += dxdy[1];
                        if (board.notInBoard(x, y)) {
                            break;
                        }
                        if (board.getGround(x, y).attribute == Attribute.Light) {
                            Move moveValue = canMove(x, y);
                            if (moveValue.canStay) {
                                moveRange.put(new Point(x, y), moveValue);
                            }
                        }
                    }
                }
            }
        }

        private String sideColor() {
            return side == 1 ? "#FF2F3542" : "#FFF1F2F6";
        }

        public void render(Room room, boolean selecting) {
            if (num == 0) {
                final float bX = (BOARD_SIZE + 0.25f) / BOARD_SIZE;
                float bY = 0.1f * (id + 1);
                if (side == 2) {
                    bY += 0.5;
                }
                if (attribute == board.secondAttribute) {
                    bY += 0.2;
                }
                room.signList.add(new TitleSign(bX, bY, 1, "⬤", this.sideColor(), 0.5f));
                room.signList.add(new TitleSign(bX, bY, 1, "⬤", attribute.color, 0.3f));
            }
            int index = room.getIndex(position.x, position.y);
            if (isRevealed) {
                room.curGrids[position.x][position.y] = side;
                room.signList.add(new TextSign(index, getName(), attribute.color));
            }
            else {
                room.curGrids[position.x][position.y] = 7;
            }
            if (selecting) {
                for (Point point : moveRange.keySet()) {
                    room.signList.add(new TextSign(room.getIndex(point.x, point.y), "○", ColorCost.NEW_PUT_POINT));
                }
            }
        }

        public int getIndex() {
            return board.getIndex(position.x, position.y);
        }

        public Ground getGround() {
            return board.getGround(position.x, position.y);
        }
    }

    private static class Board {

        public final Set<Ground> groundSet = new HashSet<>();
        public final Set<Chess> chessSet = new HashSet<>();
        public Attribute firstAttribute;
        public Attribute secondAttribute;
        public Chess selected;
        private Integer touchFrom = null;
        private Integer touchTo = null;
        private String testcase = null;

        public Board() {
            randomAttribute();
            initGround();
            initChess();
        }

        public Board(String testcase) {
            this.testcase = testcase;
            switch (testcase) {
                case "Thunder":
                    firstAttribute = Attribute.Thunder;
                    secondAttribute = Attribute.Thorn;

                    chessSet.add(new Chess(this, 1, 0, firstAttribute, 0, 9, 0));
                    chessSet.add(new Chess(this, 1, 0, firstAttribute, 1, 9, 1));
                    chessSet.add(new Chess(this, 1, 0, firstAttribute, 2, 9, 2));
                    chessSet.add(new Chess(this, 1, 0, firstAttribute, 3, 9, 3));
                    chessSet.add(new Chess(this, 2, 0, firstAttribute, 9, 9, 0));
                    chessSet.add(new Chess(this, 2, 0, firstAttribute, 8, 9, 1));
                    chessSet.add(new Chess(this, 2, 0, firstAttribute, 7, 9, 2));
                    chessSet.add(new Chess(this, 2, 0, firstAttribute, 6, 9, 3));
                    addTestBuff(firstAttribute, 2, 1, new int[]{8, 5, 9, 8, 7, 5, 6, 5, 0}, new int[]{1, 2, 2, 2, 2, 2, 2, 2, 2});
                    addTestBuff(firstAttribute, 7, 1, new int[]{8, 5, 4, 3, 2, 5, 1, 4, 7}, new int[]{1, 2, 2, 2, 2, 2, 2, 1, 1});

                    addTestBuff(firstAttribute, 2, 4, new int[]{4, 6, 9, 8, 7, 6, 6, 5, 0}, new int[]{1, 2, 2, 2, 2, 2, 2, 2, 2});
                    addTestBuff(firstAttribute, 7, 4, new int[]{4, 6, 4, 3, 2, 6, 1, 4, 7}, new int[]{1, 2, 2, 2, 2, 2, 2, 1, 1});

                    addTestBuff(firstAttribute, 2, 7, new int[]{3, 9, 9, 8, 7, 9, 6, 5, 0}, new int[]{1, 2, 2, 2, 2, 2, 2, 2, 2});
                    addTestBuff(firstAttribute, 7, 7, new int[]{3, 9, 4, 3, 2, 9, 1, 4, 7}, new int[]{1, 2, 2, 2, 2, 2, 2, 1, 1});
                    break;
                case "noMove":
                    randomAttribute();
                    initGround();
                    chessSet.add(new Chess(this, 1, 0, firstAttribute, 0, 1, 0));
                    chessSet.add(new Chess(this, 1, 0, firstAttribute, 1, 1, 1));
                    chessSet.add(new Chess(this, 1, 0, secondAttribute, 1, 0, 2));
                    chessSet.add(new Chess(this, 1, 9, secondAttribute, 0, 0, 0));
                    chessSet.add(new Chess(this, 2, 0, firstAttribute, 2, 2, 0));
                    chessSet.add(new Chess(this, 2, 9, secondAttribute, 9, 9, 0));
                    break;
                case "colorB":
                    for (int i = 0; i < Attribute.values().length; i++) {
                        Attribute value = Attribute.values()[i];
                        for (int j = 0; j < BOARD_SIZE; j++) {
                            Chess chess = new Chess(this, 1, 5, value, j, i, 0);
                            if (value == Attribute.Empty) {
                                chessSet.add(chess);
                                continue;
                            }
                            groundSet.add(new Ground(value, i, j));
                            chess.isRevealed = true;
                            chessSet.add(chess);
                        }
                    }
                    break;
                case "colorW":
                    for (int i = 0; i < Attribute.values().length; i++) {
                        Attribute value = Attribute.values()[i];
                        for (int j = 0; j < BOARD_SIZE; j++) {
                            Chess chess = new Chess(this, 2, 5, value, j, i, 0);
                            if (value == Attribute.Empty) {
                                chessSet.add(chess);
                                continue;
                            }
                            groundSet.add(new Ground(value, i, j));
                            chess.isRevealed = true;
                            chessSet.add(chess);
                        }
                    }
                    break;
            }
        }

        private static double getPointDistanceWeight(Point p) {
            return getPointDistanceWeight(p.x, p.y);
        }

        private static double getPointDistanceWeight(int x, int y) {
            return Math.pow(BOARD_SIZE, 2 * BOARD_SIZE - (Math.abs(BOARD_SIZE - 1 - 2 * x) + Math.abs(BOARD_SIZE - 1 - 2 * y)));
        }

        private void addTestBuff(Attribute attribute, int startX, int startY, int[] nums, int[] sides) {
            groundSet.add(new Ground(attribute, startX, startY));
            chessSet.add(new Chess(this, sides[0], nums[0], attribute, startX, startY, 0));

            chessSet.add(new Chess(this, sides[1], nums[1], attribute, startX - 1, startY, 0));
            chessSet.add(new Chess(this, sides[2], nums[2], attribute, startX - 1, startY - 1, 0));
            chessSet.add(new Chess(this, sides[3], nums[3], attribute, startX - 2, startY, 0));
            chessSet.add(new Chess(this, sides[4], nums[4], attribute, startX - 1, startY + 1, 0));

            chessSet.add(new Chess(this, sides[5], nums[5], attribute, startX + 1, startY, 0));
            chessSet.add(new Chess(this, sides[6], nums[6], attribute, startX + 1, startY - 1, 0));
            chessSet.add(new Chess(this, sides[7], nums[7], attribute, startX + 2, startY, 0));
            chessSet.add(new Chess(this, sides[8], nums[8], attribute, startX + 1, startY + 1, 0));

        }

        private void randomAttribute() {
            List<Attribute> choices = new ArrayList<>(Arrays.asList(Attribute.values()));
            choices.remove(Attribute.Empty);
            Collections.shuffle(choices);
            firstAttribute = choices.get(0);
            secondAttribute = choices.get(1);
        }

        private List<Point> getGridPoints() {
            List<Point> positions = new ArrayList<>();
            for (int i = 0; i < BOARD_SIZE; i++) {
                for (int j = 0; j < BOARD_SIZE; j++) {
                    positions.add(new Point(i, j));
                }
            }
            return positions;
        }

        private List<Point> getGridPoints(int[][] grounds) {
            List<Point> positions = new ArrayList<>();
            for (int i = 0; i < BOARD_SIZE; i++) {
                for (int j = 0; j < BOARD_SIZE; j++) {
                    if (grounds[i][j] == 0) {
                        positions.add(new Point(i, j));
                    }
                }
            }
            return positions;
        }

        private boolean tryAddAttributeBlockWithRand(int[][] grounds, Attribute attribute, Point origin, int rand, Set<Point> points) {
            for (int[] block : attribute.shape) {
                int x = block[0];
                int y = block[1];
                if ((rand & 1) > 0) {
                    x = x ^ y;
                    y = x ^ y;
                    x = x ^ y;
                }
                if ((rand & 2) > 0) {
                    x = -x;
                }
                if ((rand & 4) > 0) {
                    y = -y;
                }
                final int stepX = origin.x + x;
                final int stepY = origin.y + y;
                if (notInBoard(stepX, stepY)) {
                    return false;
                }
                if (grounds[stepX][stepY] != 0) {
                    return false;
                }
                points.add(new Point(stepX, stepY));
            }
            if (true) { // 能否和相同属性的块相邻 attribute.canNeighbor
                for (Point point : points) {
                    for (int[] dxdy : MOVE_DIRECTION) {
                        Point tmp = new Point(point.x + dxdy[0], point.y + dxdy[1]);
                        if (!points.contains(tmp) && isInBoard(tmp.x, tmp.y) && grounds[tmp.x][tmp.y] == attribute.value) {
                            // 不相邻
                            return false;
                        }
                    }
                }
            }
            return true;
        }

        private boolean tryAddAttributeBlock(int[][] grounds, Attribute attribute, Point origin) {
            if (grounds[origin.x][origin.y] != 0) {
                return false;
            }
            final int originRand = RANDOM.nextInt(8); //随机一个值用三位二进制分别表示是否交换xy，反转x，反转y
            int rand = originRand;
            while (true) {
                Set<Point> points = new HashSet<>();
                points.add(origin);
                if (tryAddAttributeBlockWithRand(grounds, attribute, origin, rand, points)) {
                    for (Point point : points) {
                        grounds[point.x][point.y] = attribute.value;
                    }
                    return true;
                }

                rand = (rand + 1) % 8;
                if (rand == originRand) {
                    return false;
                }
            }
        }

        private boolean isWater(final int[][] grounds, int x, int y) {
            if (notInBoard(x, y)) {
                return false;
            }
            return grounds[x][y] == Attribute.Water.value;
        }

        private double canWaterWeight(final int[][] grounds, int x, int y) {
            if (notInBoard(x, y)) {
                return 0;
            }
            if (grounds[x][y] != 0) {
                return 0;
            }
            int cnt = 0;
            boolean isFarther = false;
            for (int[] dxdy : MOVE_DIRECTION) {
                final int stepX = x + dxdy[0];
                final int stepY = y + dxdy[1];
                if (isWater(grounds, stepX, stepY)) {
                    if (!isFarther && getPointDistanceWeight(x, y) > getPointDistanceWeight(stepX, stepY)) {
                        isFarther = true;
                    }
                    cnt++;
                }
            }
            if (cnt >= 2) {
                return 10;
            }
            if (cnt == 0) {
                return 0;
            }
            return isFarther ? 60 : 40;
        }

        private Point getRandomWithDefaultWeight(List<Point> points) {
            Map<Point, Double> map = points.stream().collect(Collectors.toMap(
                    p -> p, Board::getPointDistanceWeight
            ));
            Point origin = weightSelect(map);
            return origin;
        }

        private void addAllWater(int[][] grounds, List<Point> positions, int count) {
            while (count > 0) {
                Point origin = getRandomWithDefaultWeight(positions);
                positions.remove(origin);
                grounds[origin.x][origin.y] = Attribute.Water.value;
                count--;
                while (count > 0) {
                    Map<Point, Double> validStepMap = new HashMap<>();
                    for (int i = 0; i < BOARD_SIZE; i++) {
                        for (int j = 0; j < BOARD_SIZE; j++) {
                            if (grounds[i][j] == 0) {
                                double weight = canWaterWeight(grounds, i, j);
                                validStepMap.put(new Point(i, j), weight);
                            }
                        }
                    }
                    if (validStepMap.isEmpty()) {
                        break;
                    }
                    else {
                        Point step = weightSelect(validStepMap);
                        grounds[step.x][step.y] = Attribute.Water.value;
                        count--;
                        positions.remove(step);
                    }
                }
            }
        }

        private void addAttributeGround(int[][] grounds, List<Point> positions, Attribute attribute, int count) {
            switch (attribute) {
                case Water:
                    addAllWater(grounds, positions, count);
                    break;
                case Light:
                    while (count > 0 && !positions.isEmpty()) {
                        Collections.shuffle(positions);
                        Point point = positions.remove(0);
                        if (grounds[point.x][point.y] != 0) {
                            continue;
                        }
                        int rand = RANDOM.nextInt(4);
                        int[] randDirection = MOVE_DIRECTION[rand];
                        int randDistance = RANDOM.nextInt(BOARD_SIZE - 3) + 3;
                        final int stepX = point.x + randDistance * randDirection[0];
                        final int stepY = point.y + randDistance * randDirection[1];
                        if (isInBoard(stepX, stepY) && grounds[stepX][stepY] == 0) {
                            count -= 2;
                            grounds[point.x][point.y] = attribute.value;
                            grounds[stepX][stepY] = attribute.value;
                        }
                    }
                    break;
                default:
                    while (count > 0 && !positions.isEmpty()) {
                        Collections.shuffle(positions);
                        if (tryAddAttributeBlock(grounds, attribute, positions.remove(0))) {
                            count -= attribute.shape.length + 1;
                        }
                    }
            }
        }

        private int[][] randomGround() {
            int[][] grounds = GridsUtils.createEmptyGrids(BOARD_SIZE, BOARD_SIZE);
            List<Point> positions = getGridPoints(grounds);
            addAttributeGround(grounds, positions, firstAttribute, 24);
            positions = getGridPoints(grounds);
            addAttributeGround(grounds, positions, secondAttribute, 16);
            return grounds;
        }

        private void initGround() {
            int[][] testGrid = randomGround();
            for (int i = 0; i < BOARD_SIZE; i++) {
                for (int j = 0; j < BOARD_SIZE; j++) {
                    if (testGrid[i][j] != 0) {
                        groundSet.add(new Ground(testGrid[i][j], i, j));
                    }
                }
            }
        }

        private void initChess() {
            List<Point> positions = getGridPoints();
            Collections.shuffle(positions);
            initChessZero(positions, firstAttribute);
            initChessZero(positions, secondAttribute);
            initChessByAttribute(positions, firstAttribute);
            initChessByAttribute(positions, secondAttribute);
        }

        private void initChessZero(List<Point> positions, Attribute attribute) {
            int index = 0;
            for (int side = 1; side < 3; side++) {
                for (int i = 0; i < 2; i++) {
                    Point point = positions.get(index);
                    while (getGround(point.x, point.y).attribute != Attribute.Empty) {
                        index++;
                        point = positions.get(index);
                    }
                    chessSet.add(new Chess(this, side, 0, attribute, point.x, point.y, i));
                    positions.remove(index);
                }
            }
        }

        private void initChessByAttribute(List<Point> positions, Attribute attribute) {
            for (int number = 1; number < BOARD_SIZE; number++) {
                final int count;
                if (number < 2 || number > 6) {
                    count = 2;
                }
                else {
                    count = 3;
                }
                for (int side = 1; side < 3; side++) {
                    for (int i = 0; i < count; i++) {
                        Point point = positions.remove(0);
                        chessSet.add(new Chess(this, side, number, attribute, point.x, point.y, i));
                    }
                }
            }
        }

        public boolean isInBoard(int x, int y) {
            return !notInBoard(x, y);
        }

        public boolean notInBoard(int x, int y) {
            return x < 0 || x >= BOARD_SIZE || y < 0 || y >= BOARD_SIZE;
        }

        public void render(Room room) {
            Map<String, List<Integer>> colorMap = new HashMap<>();
            for (Ground ground : groundSet) {
                ground.render(room, colorMap);
            }
            colorMap.forEach((k, v) -> {
//                room.signList.add(new AroundSign(v, k, k, 1f));
                room.signList.add(new ColorSign(v, k));
            });
            for (Chess chess : chessSet) {
                chess.render(room, chess == selected);
            }
            if (touchFrom != null) {
                if (touchTo == null) {
                    room.signList.add(new GroundSign(touchFrom, "#00000000", ColorCost.NEW_PUT_POINT, 2.0f, 0, 0));
                }
                else {
                    room.signList.add(new LineSign(touchFrom, touchTo, ColorCost.MOVE_ARROW_LIGHT, 1));
                }
            }
            if (firstAttribute != null && secondAttribute != null) {
                float titleSize = 0.3f;
                float bY = (room.curGrids.length + 0.2f) / room.curGrids.length;
                room.signList.add(new TitleSign(0, bY, 2, firstAttribute.name + ":" + firstAttribute.describe, firstAttribute.color, titleSize));
                room.signList.add(
                        new TitleSign(0.5f, bY, 2, secondAttribute.name + ":" + secondAttribute.describe, secondAttribute.color, titleSize));
            }
            else if (testcase != null) {
                for (int i = 0; i < Attribute.values().length; i++) {
                    if (i == 0) {
                        continue;
                    }
                    Attribute value = Attribute.values()[i];
                    float bY = (room.curGrids.length + 0.2f) / room.curGrids.length;
                    room.signList.add(new TitleSign((float) i / 10, bY, 2, value.name, value.color, 0.3f));
                }
            }
        }

        //@NotNull
        public Ground getGround(int x, int y) {
            for (Ground ground : groundSet) {
                if (ground.position.x == x && ground.position.y == y) {
                    return ground;
                }
            }
            return Ground.EMPTY;
        }

        public Chess getChess(int x, int y) {
            for (Chess chess : chessSet) {
                if (chess.position.x == x && chess.position.y == y) {
                    return chess;
                }
            }
            return null;
        }

        /**
         * 玩家点击棋盘
         *
         * @return 返回玩家是否结束行动
         */
        public boolean touch(int x, int y, int value) {
            if (selected == null) {
                selected = getChess(x, y);
                if (selected != null) {
                    touchTo = null;
                    touchFrom = selected.getIndex();
                    if (selected.isRevealed) {
                        selected.updateMoveRange();
                    }
                    else {
                        selected.isRevealed = true;
                        selected = null;
                        return true;
                    }
                }
                return false;
            }
            if (selected.side != value) {
                selected = null;
                return false;
            }
            if (!checkAndMove(selected.position.x, selected.position.y, x, y)) {
                selected = null;
                return false;
            }
            selected = null;
            touchTo = getIndex(x, y);
            return true;
        }

        private int getIndex(int x, int y) {
            return x * BOARD_SIZE + y;
        }

        private boolean checkKillSelf(Move move) {
            switch (move) {
                case BOOM:
                case BOOM_PASS:
                case SUICIDE:
                case SUICIDE_PASS:
                    return true;
                default:
                    return false;
            }
        }

        private boolean checkKillEnemy(Move move) {
            switch (move) {
                case EAT:
                case EAT_PASS:
                case BOOM:
                case BOOM_PASS:
                    return true;
                default:
                    return false;
            }
        }

        private boolean checkAndMove(int xFrom, int yFrom, int xTo, int yTo) {
            Ground moveGround = getGround(xFrom, yFrom);
            Chess moveChess = getChess(xFrom, yFrom);
            if (moveChess == null) {
                return false;
            }
            if (xFrom == xTo && yFrom == yTo) {
                return false;
            }
            Point newPos = new Point(xTo, yTo);
            if (!moveChess.moveRange.containsKey(newPos)) {
                return false;
            }
            Chess enemyChess = getChess(xTo, yTo);

            Move selfMove = moveChess.canMove(xTo, yTo);
            if (checkKillEnemy(selfMove)) {
                chessSet.remove(getChess(xTo, yTo));

            }

            if (enemyChess != null && (moveChess.attribute == moveGround.attribute && moveChess.attribute == Attribute.Thunder)) {
                // 移动方 雷
                for (int[] dxdy : MOVE_DIRECTION) {
                    final int x = xTo + dxdy[0];
                    final int y = yTo + dxdy[1];
                    if (notInBoard(x, y)) {
                        continue;
                    }
                    Move thunderMove = moveChess.canMove(x, y, true);
                    if (checkKillEnemy(thunderMove)) {
                        chessSet.remove(getChess(x, y));
                    }
                }
            }

            if (checkKillSelf(selfMove)) {
                chessSet.remove(moveChess);
            }
            else {
                moveChess.position.move(xTo, yTo);
            }
            return true;
        }

        /**
         * @return 0 没事，1 黑赢，2 白赢，3 平局
         */
        public int checkWin(int next) {
            int b0 = 0;
            int w0 = 0;
            boolean nextCanMove = false;
            for (Chess chess : chessSet) {
                if (chess.num == 0) {
                    if (chess.side == 1) {
                        b0++;
                    }
                    else {
                        w0++;
                    }
                }
                if (!nextCanMove) {
                    if (!chess.isRevealed) {
                        nextCanMove = true;
                    }
                    else {
                        if (chess.side == next) {
                            chess.updateMoveRange();
                            if (!chess.moveRange.isEmpty()) {
                                nextCanMove = true;
                            }
                        }
                    }
                }
            }
            if (b0 == 0 && w0 == 0) {
                return 3;
            }
            else if (b0 == 0 || (!nextCanMove && next == 1)) {
                return 2;
            }
            else if (w0 == 0 || (!nextCanMove && next == 2)) {
                return 1;
            }
            else {
                return 0;
            }
        }

        public Move checkCanMove(Chess attacker, Ground ground) {
            final Move stop;
            if (attacker.attribute == Attribute.Float && attacker.getGround().attribute == Attribute.Float) {
                // 己方 风
                stop = Move.PASS;
            }
            else {
                stop = Move.STAND;
            }
            if (attacker.attribute != Attribute.Night && ground.attribute == Attribute.Night) {
                // 己方 月
                return stop;
            }
            if (attacker.attribute != Attribute.Gold && attacker.getGround().attribute != Attribute.Gold && ground.attribute == Attribute.Gold) {
                return Move.NOT_ALLOW;
            }
            return Move.PASS;

        }

        public Move checkCanEat(Chess attacker, Chess defender, boolean isThunderEffect) {
            final Move notAllow, eat, boom, suicide;
            boolean attackerEffect = attacker.attribute == attacker.getGround().attribute;
            if (attackerEffect && attacker.attribute == Attribute.Float) {
                // 己方 风
                notAllow = Move.NOT_ALLOW_PASS;
                eat = Move.EAT_PASS;
                boom = Move.BOOM_PASS;
                suicide = Move.SUICIDE_PASS;
            }
            else if (attackerEffect && attacker.attribute == Attribute.Fire) {
                // 己方 火
                notAllow = Move.NOT_ALLOW;
                eat = Move.EAT;
                boom = Move.EAT;
                suicide = Move.BOOM;
            }
            else {
                notAllow = Move.NOT_ALLOW;
                eat = Move.EAT;
                boom = Move.BOOM;
                suicide = Move.SUICIDE;
            }

            if (!defender.isRevealed) {
                // 暗子不允许被吃
                return notAllow;
            }

            if (defender.side == attacker.side) {
                // 己方的棋子会挡路
                return notAllow;
            }

            if (defender.getGround().attribute == Attribute.Gold && attacker.attribute != Attribute.Gold
                    && attacker.getGround().attribute != Attribute.Gold) {
                // 金地形效果
                return notAllow;
            }

            if (defender.num == 0 && isThunderEffect) {
                // 雷效果不能吃0
                return notAllow;
            }

            if (defender.num == 0 && minNum(attacker.side) != attacker.num) {
                // 敌方的0有保护
                return notAllow;
            }

            if (!attackerEffect && attacker.getGround().attribute == Attribute.Thorn) {
                // 木地形阻止吃子
                return notAllow;
            }

            final boolean defenderEffect = defender.attribute == defender.getGround().attribute;

            if (!defenderEffect && defender.getGround().attribute == Attribute.Earth) {
                // 敌方 土
                return eat;
            }

            if (minNum(attacker.side) == attacker.num && defender.num == 0) {
                return eat;
            }
            // 普通吃子
            if (attacker.num % defender.num == 0 || defender.num % attacker.num == 0) {
                return boom;
            }
            if (attacker.num > defender.num) {
                return eat;
            }
            else {
                return suicide;
            }
        }

        private int minNum(int side) {
            int minNum = Integer.MAX_VALUE;
            for (Chess chess : chessSet) {
                if (chess.isRevealed && chess.num > 0 && chess.side == side) {
                    minNum = Math.min(minNum, chess.num);
                }
            }
            return minNum;
        }
    }
}
